#if defined(_MSC_VER)
#pragma once
#endif

#ifndef LM_CORE_POINT_H
#define LM_CORE_POINT_H

#include "lm.h"
#include "vector2.h"
#include "vector3.h"
#include "normal.h"

class Point2
{
public:
	Point2() : x(0), y(0) { }
	Point2(float val) : x(val), y(val) { }
	Point2(float vx, float vy) : x(vx), y(vy) { }
	explicit Point2(const Vector2 &v);
#ifndef NDEBUG
	Point2(const Point2 &p) : x(p.x), y(p.y) { }
	Point2 &operator=(const Point2 &p) {
		x = p.x; y = p.y;
		return *this;
	}
#endif
	bool operator==(const Point2 &p) const {
		return x == p.x && y == p.y;
	}
	bool operator!=(const Point2 &p) const {
		return ! ((*this) == p);
	}
	// may use for interpolation
	Point2 operator+(const Point2 &p) const {
		return Point2(x+p.x, y+p.y);
	}
	Point2 &operator+=(const Point2 &p) {
		x += p.x; y += p.y;
		return *this;
	}
	Point2 operator+(const Vector2 &v) const {
		return Point2(x+v.x, y+v.y);
	}
	Point2 &operator+=(const Vector2 &v) {
		x += v.x; y += v.y;
		return *this;
	}
	Vector2 operator-(const Point2 &p) const {
		return Vector2(x-p.x, y-p.y);
	}
	Point2 operator-(const Vector2 &v) const {
		return Point2(x-v.x,y-v.y);
	}
	Point2 &operator-=(const Vector2 &v) {
		x -= v.x; y -= v.y;
		return *this;
	}
	Point2 operator*(float f) const {
		return Point2(x*f, y*f);
	}
	Point2 &operator*=(float f) {
		x *= f; y *= f;
		return *this;
	}
	Point2 operator/(float f) const {
		if(f == 0)
			Warning("Division by 0 in file %s, line %d\n", __FILE__, __LINE__);
		float invf = (float)1 / f;
		return Point2(x*invf, y*invf);
	}
	Point2 &operator/=(float f) {
		if(f == 0)
			Warning("Division by 0 in file %s, line %d\n", __FILE__, __LINE__);
		float invf = (float)1 / f;
		x *= invf; y *= invf;
		return *this;
	}
	Point2 operator-() const {
		return Point2(-x, -y);
	}
	float operator[](int i) const {
		Assert(i >= 0 && i <= 1);
		return (&x)[i];
	}
	float &operator[](int i) {
		Assert(i >= 0 && i <= 1);
		return (&x)[i];
	}
	bool HasNaNs() const {
		return isnan(x) || isnan(y);
	}
	std::string ToString() const {
		std::ostringstream oss;
		oss << "Point2 " << "[" << x << ", " << y << "]";
		return oss.str();
	}
	float x, y;
};
class Point3
{
public:
	Point3() : x(0), y(0), z(0) { }
	Point3(float val) : x(val), y(val), z(val) { }
	Point3(float vx, float vy, float vz) : x(vx), y(vy), z(vz) { }
	explicit Point3(const Vector3 &v);
#ifndef NDEBUG
	Point3(const Point3 &p) : x(p.x), y(p.y), z(p.z) { }
	Point3 &operator=(const Point3 &p) {
		x = p.x; y = p.y; z = p.z;
		return *this;
	}
#endif
	bool operator==(const Point3 &p) const {
		return x == p.x && y == p.y && z == p.z;
	}
	bool operator!=(const Point3 &p) const {
		return ! ((*this) == p);
	}
	// may use for interpolation
	Point3 operator+(const Point3 &p) const {
		return Point3(x+p.x, y+p.y, z+p.z);
	}
	Point3 &operator+=(const Point3 &p) {
		x += p.x; y += p.y; z += p.z;
		return *this;
	}
	Point3 operator+(const Vector3 &v) const {
		return Point3(x+v.x, y+v.y, z+v.z);
	}
	Point3 &operator+=(const Vector3 &v) {
		x += v.x; y += v.y; z += v.z;
		return *this;
	}
	Point3 operator+(const Normal &n) const {
		return Point3(x+n.x, y+n.y, z+n.z);
	}
	Point3 &operator+=(const Normal &n) {
		x += n.x; y += n.y; z += n.z;
		return *this;
	}
	Vector3 operator-(const Point3 &p) const {
		return Vector3(x-p.x, y-p.y, z-p.z);
	}
	Point3 operator-(const Vector3 &v) const {
		return Point3(x-v.x, y-v.y, z-v.z);
	}
	Point3 &operator-=(const Vector3 &v) {
		x -= v.x; y -= v.y; z -= v.z;
		return *this;
	}
	Point3 operator*(float f) const {
		return Point3(x*f, y*f, z*f);
	}
	Point3 &operator*=(float f) {
		x *= f; y *= f; z *= f;
		return *this;
	}
	Point3 operator/(float f) const {
		if(f == 0)
			Warning("Division by 0 in file %s, line %d\n", __FILE__, __LINE__);
		float invf = (float)1 / f;
		return Point3(x*invf, y*invf, z*invf);
	}
	Point3 &operator/=(float f) {
		if(f == 0)
			Warning("Division by 0 in file %s, line %d\n", __FILE__, __LINE__);
		float invf = (float)1 / f;
		x *= invf; y *= invf; z *= invf;
		return *this;
	}
	Point3 operator-() const {
		return Point3(-x, -y, -z);
	}
	float operator[](int i) const {
		Assert(i >= 0 && i <= 2);
		return (&x)[i];
	}
	float &operator[](int i) {
		Assert(i >= 0 && i <= 2);
		return (&x)[i];
	}
	bool HasNaNs() const {
		return isnan(x) || isnan(y) || isnan(z);
	}
	std::string ToString() const {
		std::ostringstream oss;
		oss << "Point3 " << "[" << x << ", " << y << ", "<< z << "]";
		return oss.str();
	}
	float x, y, z;
};

#endif	// LM_CORE_POINT_H